Skip to content

feat: working_dir handler field, per-process concurrency, per-instance audio sinks#16

Merged
gabrielgad merged 3 commits into
mainfrom
feat/working-dir-handler-field
Jul 3, 2026
Merged

feat: working_dir handler field, per-process concurrency, per-instance audio sinks#16
gabrielgad merged 3 commits into
mainfrom
feat/working-dir-handler-field

Conversation

@gabrielgad

Copy link
Copy Markdown
Collaborator

Foundation of the split PR stack (#12#13#14#15 are based on this branch). Three commits:

  • handler: optional working_dir — override the game process cwd for launcher-shim titles whose real binary lives in a subdir but must run with cwd higher up (e.g. native Frozenbyte titles).
  • launch: per-process concurrency — namespaced scratch (tmp/<launch_ns>) + per-launch window ownership so two concurrent splitux processes don't collide on scratch dirs or grab each other's gamescope windows.
  • audio + concurrency — per-instance virtual capture sinks (so each together stream taps only its own game's audio) + crash-safe orphan cleanup (pid-aware scratch reaping).

Merge this first; the four split/* PRs then stack on top in order.

Launcher-shim games run the real binary from a subdir but need cwd at a higher
level — e.g. native Frozenbyte titles: the game binary is in
_enchanted_edition_/bin/ while its data root (and goldberg's GseAppPath) is
_enchanted_edition_, and the game's own config-GUI launcher can't be driven by
gamepad-only together seats.

Add an optional `working_dir` handler field (relative to the mounted game root)
that overrides the cwd derivation in build_cmds. Unset = today's behavior (the
exec's parent dir). Generic — replaces per-game wrapper scripts for the whole
launcher-shim class:

  exec: _enchanted_edition_/bin/trine1_linux_32bit
  working_dir: _enchanted_edition_
  env: "LD_LIBRARY_PATH=lib/lib32"
…dow ownership

splitux could only run ONE session per host: every launch keyed its scratch by
instance index (tmp/game-0, work-0, <backend>-overlay-0), so a 2nd concurrent
launch collided on the fuse-overlayfs mounts ("mount failed"); teardown unmounted
ALL tmp/game-* globally; the startup unit-sweep stopped other live sessions; and
the niri WM grabbed EVERY gamescope window (stacking all sessions into one column
+ cross-repositioning that crashed games).

Each `splitux launch` is its own process with one active launch, so namespace all
per-launch state by a per-process launch id:
- paths: set_launch_ns() / launch_tmp_dir() = tmp/<launch_id>; scratch (game-N,
  work-N, <backend>-overlay-N, game-patches) now lives under it. overlays.rs,
  build_cmds.rs and backend overlay all route through it (mount + cwd agree).
- teardown: fuse_overlayfs_unmount_gamedirs() is launch-scoped (only this
  launch's tmp/<ns>, then removes it) — concurrent sessions untouched. session.rs
  end-of-launch cleanup uses it instead of the blanket clear_tmp().
- startup: sweep_orphan_units() skips units whose owner pid is still alive (other
  live sessions); reap_orphan_launch_scratch() removes only DEAD-pid tmp/<ns>.
- WM: niri get_gamescope_windows() filters to windows whose pid cgroup carries
  THIS launch's namespace (splitux-<ns>), so each session manages only its own
  gamescope window. Falls back to all when unscoped.

Validated: 3 native couch games launched concurrently — distinct namespaces, 6
seats, each WM "Found 1 window" + mapped, no mount collision.
…eanup

Per-instance capture/mute virtual sinks for both PulseAudio and PipeWire,
with orphan-sink reaping that recovers from dead launches without
disturbing live concurrent sessions. Threads per-instance audio and
namespaced scratch through launch (scope/build_cmds/execute), bwrap base,
and together so multiple sessions run isolated side by side.
@gabrielgad
gabrielgad merged commit de0c078 into main Jul 3, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant